Johannes Schauer: Let's Encrypt with Pound on Debian
TLDR: mister-muffin.de (and all its subdomains), bootstrap.debian.net and
binarycontrol.debian.net are now finally signed by "Let's Encrypt Authority X1"
\o/
I just tried out the letsencrypt client Debian packages prepared by Harlan
Lieberman-Berg which can be found here:
My server setup uses Pound as a reverse proxy in
front of a number of LXC based containers running the actual services.
Furthermore, letsencrypt only supports Nginx and Apache for now, so I had to
manually setup things anyways. Here is how.
After installing the Debian packages I built from above git repositories, I ran
the following commands:
$ mkdir -p letsencrypt/etc letsencrypt/lib letsencrypt/log
$ letsencrypt certonly --authenticator manual --agree-dev-preview \
--server https://acme-v01.api.letsencrypt.org/directory --text \
--config-dir letsencrypt/etc --logs-dir letsencrypt/log \
--work-dir letsencrypt/lib --email josch@mister-muffin.de \
--domains mister-muffin.de --domains blog.mister-muffin.de \
--domains [...]
I created the letsencrypt
directory structure to be able to run letsencrypt
as a normal user. Otherwise, running this command would require access to
/etc/letsencrypt
and others. Having to set this up and pass all these
parameters is a bit bothersome but there is an upstream
issue about making this
easier when using the "certonly" option which in princible should not require
superuser privileges.
The --server
option is necessary for now because "Let's Encrypt" is still in
beta and one needs to register for
it.
Without the --server
option one will get an untrusted certificate from the
"happy hacker fake CA".
The letsencrypt
program will then ask me for my agreement to the Terms of
Service and then, for each domain I specified with the --domains
option
present me the token content and the location under each domain where it
expects to find this content, respectively. This looks like this each time:
-------------------------------------------------------------------------------
NOTE: The IP of this machine will be publicly logged as having requested this
certificate. If you're running letsencrypt in manual mode on a machine that is
not your server, please ensure you're okay with that.
Are you OK with your IP being logged?
-------------------------------------------------------------------------------
(Y)es/(N)o: Y
Make sure your web server displays the following content at
http://mister-muffin.de/.well-known/acme-challenge/XXXX before continuing:
"header": "alg": "RS256", "jwk": "e": "AQAB", "kty": "RSA", "n": "YYYY" , "payload": "ZZZZ", "signature": "QQQQ"
Content-Type header MUST be set to application/jose+json.
If you don't have HTTP server configured, you can run the following
command on the target server (as root):
mkdir -p /tmp/letsencrypt/public_html/.well-known/acme-challenge
cd /tmp/letsencrypt/public_html
echo -n ' "header": "alg": "RS256", "jwk": "e": "AQAB", "kty": "RSA", "n": "YYYY" , "payload": "ZZZZ", "signature": "QQQQ" ' > .well-known/acme-challenge/XXXX
# run only once per server:
$(command -v python2 command -v python2.7 command -v python2.6) -c \
"import BaseHTTPServer, SimpleHTTPServer; \
SimpleHTTPServer.SimpleHTTPRequestHandler.extensions_map = '': 'application/jose+json' ; \
s = BaseHTTPServer.HTTPServer(('', 80), SimpleHTTPServer.SimpleHTTPRequestHandler); \
s.serve_forever()"
Press ENTER to continue
For brevity I replaced any large base64 encoded chunks of the messages with
YYYY
, ZZZZ
and QQQQ
. The token location is abbreviated with XXXX
.
After temporarily stopping Pound on my webserver I created the directory
/tmp/letsencrypt/public_html/.well-known/acme-challenge
and then opened two
shells on my server, both at /tmp/letsencrypt/public_html
. In one, I kept a
tiny HTTP server running (like the suggested Python SimpleHTTPServer which will
also work if one has Python installed). In the other I copy pasted the echo
line that the letsencrypt
program suggested me to run.
I had to copypaste that echo
command for each domain I wanted to verify. This
could easily be automated, so I filed an issue about
this with upstream.
It seems that the letsencrypt servers query each of these tokens twice: once
directly each time after having hit enter after seeing the message above and
another time once all tokens are in place.
At the end of this ordeal I get:
2015-11-04 11:12:18,409:WARNING:letsencrypt.client:Non-standard path(s), might not work with crontab installed by your operating system package manager
IMPORTANT NOTES:
- If you lose your account credentials, you can recover through
e-mails sent to josch@mister-muffin.de.
- Congratulations! Your certificate and chain have been saved at
letsencrypt/etc/live/mister-muffin.de/fullchain.pem. Your cert will
expire on 2016-02-02. To obtain a new version of the certificate in
the future, simply run Let's Encrypt again.
- Your account credentials have been saved in your Let's Encrypt
configuration directory at letsencrypt/etc. You should make a
secure backup of this folder now. This configuration directory will
also contain certificates and private keys obtained by Let's
Encrypt so making regular backups of this folder is ideal.
I can now scp the content of letsencrypt/etc/live/mister-muffin.de/*
to my
server. Unfortunately, Pound (and also my ejabberd XMPP server) requires the
private key to be in the same file as the certificate and the chain, so on the
server I also had to do:
cat /etc/ssl/private/privkey.pem /etc/ssl/private/fullchain.pem > /etc/ssl/private/private_fullchain.pem
And edit the Pound config to use /etc/ssl/private/private_fullchain.pem
. But
that's all, folks!
EDIT
It seems that manually copying over the echo commands as I described above is
not necessary. Instead of using the certonly
plugin, I can use the webroot
plugin. That plugin takes the --webroot-path
option and will copy the tokens
to there. Since my webroot is on a remote machine, I could just mount it
locally via sshfs and pass the mountpoint as --webroot-path
.
That I didn't realize that the webroot plugin does what I want (and not the
certonly plugin) can easily be explained by the only documentation of the
webroot plugin in the help output and the man page generated from it being
"Webroot Authenticator" which is not very helpful.
Another user seems to have run into similar
problems. Better
documenting the plugins so that these situations can be prevented in the future
is tracked in this upstream
bug.